############copyright############
############ SCU  2020#####
############Zhou Zunlong#############

from time import time
import random
import datetime
import time
from urllib.parse import urlparse
from Blockchain import Blockchain
from multiprocessing import Lock
from multiprocessing.sharedctypes import RawArray    #共享内存
from multiprocessing import Process,Pool, Semaphore
from multiprocessing.managers import BaseManager
import multiprocessing

from Miner import func
from myRPC import myRPCProtocol,convert2RPC
from MyOwnPeer2PeerNode import MyOwnPeer2PeerNode
from utils import read_config
# 全局变量
config_file = "config.ini"

config = read_config(config_file)
ip_address = config["info"]["ip"]
port = int(config["info"]["port"])
account = config["info"]["account"]
ecllipse_loss = int(config["status"]["ecllipse_loss"])
probability = float(config["status"]["conflict_pro"])


class Master(myRPCProtocol):
    def __init__(self):
        super(Master, self).__init__()
        self.clear()
        print("============BlockChain===============")
        self.blockchain = Blockchain()
        self.blockchain.genesis_block()
        self.connectServer()

        self.ID = account

    def pow(self):
        startclock = time.process_time()

        Z = int(config["system"]["z"])  # 探矿难度

        # nPro = 5                       #矿工进程数量                       这里本来是5，但是奇怪的是要用这个底下矿工算力比例只有四个矿工
        nPro = int(config["system"]["nPro"])
        sem = Semaphore(nPro)  # 信号量,允许并行挖矿的矿工数量

        # 这里的'i','d'表示变量类型；'d' 表示双精度浮点数， 'i' 表示有符号整数。
        self.S = multiprocessing.Value("i", 0)  # S=1表示挖矿成功 这里是共享变量
        res = multiprocessing.Value("i", -1)  # S=1表示挖矿成功 这里是共享变量
        T = RawArray("i", [0 for i in range(nPro)])  # T[i]代表该块矿工尝试挖矿次数
        B = RawArray("i", [0 for i in range(nPro)])  # B[i]=1代表开始挖矿
        U = RawArray("i", [0 for i in range(nPro)])  # U[i]记录每个矿工挖矿的时间片次数
        M = RawArray("d", [0 for i in range(nPro)])  # M[i]记录每个矿工的比特币收益
        Share = RawArray("i", [0 for i in range(nPro)])  # Share用来分配出块后的收益

        MT = RawArray("i", [0 for i in range(nPro)])  # 挖中次数
        blockdetail = multiprocessing.Manager().dict()

        nPow = eval(config["system"]["nPow"])  # 0为被攻击矿池，1为分配的攻击算计，2为攻击者正常挖矿的算力，3为其他 nPow = (0.4, 0.1, 0.4, 0.1)
        tUnit = int(config["system"]["tUnit"]) * ecllipse_loss  # 每个时间片内挖矿最大次数
        tCycle = 10000  # 实验设置的出块数量，初始10000

        starttime = datetime.datetime.now()
        ti = 0

        while ti < tCycle:  # ti出块数量
            while str(self.blockbegin) != '[\'begin\']':  # 如果挖矿开始标志没变，就等待
                zzl = 1
            try:
                while (True):
                    self.blockdata.pop()
            except:
                print("已清空")
            watcher = Process(target=self.watch, args=())
            watcher.start()
            ti = ti + 1
            self.S.value = 0  # 重置挖矿成功标志
            for i in range(nPro):  # 重置参数
                B[i] = 0
            # 如果发起了BDos攻击且自己属于小矿池，就放弃这次挖矿
            if float(eval(str(self.conflict))[-1]) < probability and tUnit < 80:
                # 等其他人挖完
                while len(str(self.blockdata).split(',')) < 2:
                    pass
                print("放弃")
                temp = str(self.blockdata).replace(' ', '')[1:-1].split(',')
                self.recordIncome(M)
                for id in range(nPro):
                    Share[id] = 0

                # 只取先放进去的数据
                self.recordBlockchain(self.blockchain.new_block(int(temp[0]), temp[1][1:-1]))
                self.ev.append(account)

                watcher.terminate()
                continue
            self.mining(Z, starttime, self.S, B, U, nPro, nPow, tUnit, T, ti, Share, M, MT, blockdetail,
                   self.blockchain.prev_hash, res)
            if str(self.blockbegin) == '[\'begin\']':

                # 防止冲突，可能同时挖到然后进到这里，导致pop empty list 错误
                try:
                    self.blockbegin.pop()
                except:
                    print("对方先挖到了")
                else:
                    # 如果大矿池挖到了，就可能会有分叉，需要合并冲突
                    self.blockbegin.append('wait')

                    self.blockdata.append(blockdetail["proof"])
                    self.blockdata.append(blockdetail["trans"])
                    if random.random() > 0.5 or float(eval(str(self.conflict))[-1]) > probability:
                        for i in range(nPro):
                            M[i] = M[i] + 12.5 * Share[i] / (sum(Share))
                        self.blockminer.append(account)
                        self.recordLedger(blockdetail["id"])
                    else:
                        self.blockminer.append("BDos")
            temp = str(self.blockdata).replace(' ', '')[1:-1].split(',')
            self.recordIncome(M)
            for id in range(nPro):
                Share[id] = 0

            # 只取先放进去的数据
            self.recordBlockchain(self.blockchain.new_block(int(temp[0]), temp[1][1:-1]))
            self.ev.append(account)

            watcher.terminate()
        print(self.blockchain.get_chain)
        # Z-探矿难度, strTarget-目标串起始字符串“0000”, starttime=开始时间, S-共享的挖矿成功标志, B-每个矿工开始标志, U-矿工挖矿时间片次数
        # nPro-人数, nPow-算力比例, tUnit-每个时间片挖矿最大次数, T-尝试次数, ti-出块数量, Share-出块收益, M-每个人的收入

        endtime = datetime.datetime.now()

        print("程序运行时间 %f 秒" % (endtime - starttime).seconds)

        print("程序运行CPU时间 %f 秒" % (time.process_time() - startclock))

        print("~~~~~~~~~~~~~~~~~~~~~~Mining is over~~~~~~~~~~~~~~~~~~~~~~")

        for i in range(nPro):
            print(MT[i])
        # drawRes()  # 绘制结果


    def connectServer(self):
        self.mgr = BaseManager(address=(ip_address, port), authkey=b'pwd')
        self.mgr.register("getUser")
        self.mgr.register("BlockBegin")
        self.mgr.register("BlockMiner")
        self.mgr.register("ev")
        self.mgr.register("BlockDetail")
        self.mgr.register("BDos")
        self.mgr.connect()
        self.blockbegin = self.mgr.BlockBegin()
        self.blockminer = self.mgr.BlockMiner()
        self.ev = self.mgr.ev()
        self.blockdata = self.mgr.BlockDetail()
        self.conflict = self.mgr.conflict()

    def clear(self):
        with open('./ledger.txt', 'w') as f:
            f.truncate()
        with open('./income.txt', 'w') as f:
            f.truncate()
        with open('./blockchain.txt', 'w') as f:
            f.truncate()


    def mining (self,Z,starttime,S,B, U, nPro,nPow,tUnit,T,Ti, Share, M, MT,blockdetail,prev_hash,res):
        ps = [Process(target=func, args=(i,Z,starttime,S,B, U,nPro,nPow,tUnit,T,Ti,Share,M,MT,blockdetail,prev_hash,res)) for i in range(nPro)]
        for p in ps:
            p.start()

        for iP in range(nPro):
            B[iP] = 1                        #B[iP]=1代表第iP个进程开始挖矿

        for p in ps:
            p.join()			#让主进程等子进程执行完

    def recordIncome(self,M):
        with open('./income.txt', 'a') as f:
            res=""
            for i in M:
                res=res+str(i)+","
            f.write(res[0:-1]+"\n")
            f.close()
    def recordLedger(self, ids):
        with open('./ledger.txt', "a") as f:
            f.write(str(ids) + "\n")
    def recordBlockchain(self,block):
        with open('./blockchain.txt', 'a') as f:
            f.write("{"+str(block['index'])+","+str(block['transactions'])+","+str(block['previous_hash'])+","+str(block['proof'])+"}\n")
            f.close()

    # 如果别人挖到了马上暂停 S.value
    def watch(self):
        while len(str(self.blockdata).split(',')) < 2:
            pass
        self.S.value = 1

def communicate():
    global node
    node = MyOwnPeer2PeerNode("127.0.0.1",8003)
    node.start()
    print("node start")

if __name__ == '__main__':
    p = Process(target=communicate, args=())
    p.start()

    master = Master()
    master.pow()



